#ifndef FREE_GLYPH_HPP
#define FREE_GLYPH_HPP

#include <cstdlib>
#include <cassert>
#include "linear_algo.hpp"

#define GLEW_STATIC
#include <GL/glew.h> // -------------- glew

#define GL_GLEXT_PROTOTYPES
#include <SDL_opengl.h>
// ------------------------------- freetype2
#include <freetype/config/ftheader.h>
#include FT_FREETYPE_H
#include <freetype/config/ftoption.h>
#include <freetype/fttypes.h>
// -------------------------------------
#include "simple_renderer.hpp"

#define FREE_GLYPH_FONT_SIZE 64

// REF LINKS: // https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02
struct Glyph_Metric
{
    float ax; // advance x
    float ay; // advance y

    float bw; // bitmap width
    float bh; // bitmap rows

    float bl; // bitmap left
    float bt; // bitmap top

    float tx; // x offset of glyph in texture coordinates
};

// capacity
#define GLYPH_METRICS_CAPACITY 128

struct Free_Glyph_Atlas
{
    FT_UInt atlas_width;
    FT_UInt atlas_height;
    GLuint glyphs_texture;
    Glyph_Metric metrics[GLYPH_METRICS_CAPACITY];
};
// -------------------------------------------------- expose apis
void FreeGlyphAtlasInit(Free_Glyph_Atlas *atlas, FT_Face face);
float FreeGlyphAtlasCursorPos(const Free_Glyph_Atlas *atlas,
                              const char *text, size_t text_size, Vec2f pos, size_t col);

void FreeGlyphAtlasMeasureLineSized(Free_Glyph_Atlas *atlas,
                                    const char *text, size_t text_size, Vec2f *pos);

void FreeGlyphAtlasRenderLineSized(Free_Glyph_Atlas *atlas,
                                   Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, Vec4f color);

// free_glyph impl
#define FREE_GLYPH_IMPL // just a macro, ignore it should be ok
// --------------------------------------------- apis impl
void FreeGlyphAtlasInit(Free_Glyph_Atlas *atlas, FT_Face face)
{
    FT_Int32 load_flags = FT_LOAD_RENDER |
                          FT_LOAD_TARGET_(FT_RENDER_MODE_SDF);

    for (int i = 32; i < 128; ++i)
    {
        if (FT_Load_Char(face, i, load_flags))
        {
            fprintf(stderr, "ERROR: could not load glyph of a character with code %d\n", i);
            exit(1);
        }

        atlas->atlas_width += face->glyph->bitmap.width;
        if (atlas->atlas_height < face->glyph->bitmap.rows)
        {
            atlas->atlas_height = face->glyph->bitmap.rows;
        }
    }

    glActiveTexture(GL_TEXTURE0);
    glGenTextures(1, &atlas->glyphs_texture);
    glBindTexture(GL_TEXTURE_2D, atlas->glyphs_texture);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(
        GL_TEXTURE_2D,
        0,
        GL_RED,
        (GLsizei)atlas->atlas_width,
        (GLsizei)atlas->atlas_height,
        0,
        GL_RED,
        GL_UNSIGNED_BYTE,
        NULL);

    int x = 0;
    for (int i = 32; i < 128; ++i)
    {
        if (FT_Load_Char(face, i, load_flags))
        {
            fprintf(stderr, "ERROR: could not load glyph of a character with code %d\n", i);
            exit(1);
        }

        if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL))
        {
            fprintf(stderr, "ERROR: could not render glyph of a character with code %d\n", i);
            exit(1);
        }

        atlas->metrics[i].ax = face->glyph->advance.x >> 6;
        atlas->metrics[i].ay = face->glyph->advance.y >> 6;

        atlas->metrics[i].bw = face->glyph->bitmap.width;
        atlas->metrics[i].bh = face->glyph->bitmap.rows;
        atlas->metrics[i].bl = face->glyph->bitmap_left;
        atlas->metrics[i].bt = face->glyph->bitmap_top;
        atlas->metrics[i].tx = (float)x / (float)atlas->atlas_width;

        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTextureSubImage2D(
            GL_TEXTURE_2D,
            0,
            x,
            0,
            face->glyph->bitmap.width,
            face->glyph->bitmap.rows,
            GL_RED,
            GL_UNSIGNED_BYTE,
            face->glyph->bitmap.buffer);

        x += face->glyph->bitmap.width;
    }
}

float FreeGlyphAtlasCursorPos(const Free_Glyph_Atlas *atlas, const char *text, size_t text_size, Vec2f pos, size_t col)
{
    for (size_t i = 0; i < text_size; ++i)
    {
        if (i == col)
        {
            return pos.x;
        }

        size_t glyph_index = text[i];
        if (glyph_index >= GLYPH_METRICS_CAPACITY)
        {
            glyph_index = '?';
        }

        Glyph_Metric metric = atlas->metrics[glyph_index];
        pos.x += metric.ax;
        pos.y += metric.ay;
    }

    return pos.x;
}

void FreeGlyphAtlasMeasureLineSized(Free_Glyph_Atlas *atlas,
                                    const char *text, size_t text_size, Vec2f *pos)

{
    for (size_t i = 0; i < text_size; ++i)
    {
        size_t glyph_index = text[i];
        // todos: support for glyphs outside of ASCII range
        if (glyph_index >= GLYPH_METRICS_CAPACITY)
        {
            glyph_index = '?';
        }
        Glyph_Metric metric = atlas->metrics[glyph_index];

        pos->x += metric.ax;
        pos->y += metric.ay;
    }
}

void FreeGlyphAtlasRenderLineSized(Free_Glyph_Atlas *atlas,
                                   Simple_Renderer *sr, const char *text, size_t text_size, Vec2f *pos, Vec4f color)
{
    for (size_t i = 0; i < text_size; ++i)
    {
        size_t glyph_index = text[i];
        // todos: support for glyphs outside of ASCII range
        if (glyph_index >= GLYPH_METRICS_CAPACITY)
        {
            glyph_index = '?';
        }
        Glyph_Metric metric = atlas->metrics[glyph_index];
        float x2 = pos->x + metric.bl;
        float y2 = -pos->y - metric.bt;
        float w = metric.bw;
        float h = metric.bh;

        pos->x += metric.ax;
        pos->y += metric.ay;

        simple_renderer_image_rect(
            sr,
            vec2f(x2, -y2),
            vec2f(w, -h),
            vec2f(metric.tx, 0.0f),
            vec2f(metric.bw / (float)atlas->atlas_width, metric.bh / (float)atlas->atlas_height),
            color);
    }
}

#endif // FREE_GLYPH_HPP